package de.farbstreich.tozen2.server;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Date;
import java.util.HashMap;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.WindowConstants;

import de.farbstreich.tozen2.common.TozenMessageType;
import de.farbstreich.tozen2.common.TozenProtocolMessage;
import de.farbstreich.tozen2.server.PlayerThread.PlaceEnum;
import de.farbstreich.tozen2.server.games.Game;
import de.farbstreich.tozen2.server.games.GameWrapper;
import de.farbstreich.tozen2.server.games.NoMorePlayerException;

public class TozenServer extends JFrame {

	HashMap<Integer, PlayerThread> players;
	HashMap<Integer, Game> games;
	HashMap<PlayerThread, Long> pingTimer;
	ServerSocket socket;

	/**
	 * The Constructor creates the server and listened at {@value} port
	 * 
	 * @param port
	 */
	public TozenServer(int port) {
		players = new HashMap<Integer, PlayerThread>();
		games = new HashMap<Integer, Game>();
		pingTimer = new HashMap<PlayerThread, Long>();

		/*
		 * Debug.initalize(); Debug.format(true); Debug.setToCommandLine();
		 */
		
		
		/**
		 * Just a little GUI
		 */
		this.setLayout(new BorderLayout());
		JButton button = new JButton("Kill server");
		final TozenServer parent = this;
		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				parent.kill();
			}
		});
		this.add(button, BorderLayout.PAGE_START);

		final JTextArea textarea = new JTextArea();
		final JScrollPane scrollPane = new JScrollPane(textarea);
		
		this.add(scrollPane);
		this.setVisible(true);
		this.setTitle("Tozen^2 Server");
		this.setSize(600, 200);
		this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		this.addWindowListener(new WindowAdapter(){
			@Override
			public void windowClosing(WindowEvent arg0) {
				parent.kill();
			}
			
		});
		PrintStream outStream = new PrintStream(new OutputStream() {

			@Override
			public void write(int b) throws IOException {
				textarea.append(String.valueOf((char) b));
				scrollPane.getVerticalScrollBar().setValue(
						scrollPane.getVerticalScrollBar().getMaximum()
			            );

				
			}
		});
		System.setOut(outStream);
		System.setErr(outStream);
		/**
		 * End of GUI implemetation
		 */
		
		new PingThread(pingTimer);

		try {
			try {
				this.socket = new ServerSocket(port);
			} catch (BindException e1) {
				System.out.println("A other programm is using this socket.");
				this.kill();
				return;
			}
			System.out.println("tozen^2 server is running");
			System.out.println("listen on " + InetAddress.getLocalHost().getHostAddress()+":"+port + "...");
			while (! socket.isClosed()) {
				try {
					Socket tSocket = null;
					tSocket = socket.accept();
					PlayerThread tPlayerThread = new PlayerThread(tSocket, this);
					tPlayerThread.start();
					players.put(tPlayerThread.getPlayerId(), tPlayerThread);
					pingTimer.put(tPlayerThread, new Date().getTime());
					this.pingReset(tPlayerThread);
					System.out.println("player nr. " + tPlayerThread.getPlayerId()
							+ " connected");
				} catch (SocketException e) {
					System.out.println("The socket is closed...");
					this.kill();
					return;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			this.kill();
			return;
		} 
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new TozenServer(12399);
	}

	/**
	 * Returns whether the name is unique or not
	 * 
	 * @param name
	 * @return
	 */
	public boolean isUnuiquePlayerName(String name) {
		for (PlayerThread player : this.players.values())
			if (player.getPlayerName() != null
					&& player.getPlayerName().equals(name))
				return false;
		return true;
	}

	/**
	 * Sends a TozenProtocolMessage to all players on this server
	 * 
	 * @param msg
	 */
	public void boradcastProtocolMessage(TozenProtocolMessage msg) {
		for (PlayerThread p : this.players.values())
			p.sendProtocolMessage(msg);
	}

	/**
	 * Sends a list of all players to all players. Use this method if you change
	 * player informations, to update all clients.
	 */
	public void sendPlayersList() {
		TozenProtocolMessage msg = new TozenProtocolMessage(
				TozenMessageType.PLAYERS);

		for (PlayerThread player : this.players.values()) {
			if (player.getPlace() == PlaceEnum.LOBBY) {
				TozenProtocolMessage.player p = msg.new player();
				p.name = player.getPlayerName();
				p.id = player.getPlayerId();
				p.ingame = false;
				msg.players.add(p);
			} else if (player.getPlace() == PlaceEnum.GAME) {
				TozenProtocolMessage.player p = msg.new player();
				p.name = player.getPlayerName();
				p.id = player.getPlayerId();
				p.ingame = true;
				msg.players.add(p);
			}
		}

		this.boradcastProtocolMessage(msg);
	}

	/**
	 * Sends a list of all open games to all players.
	 */
	public void sendGamesList() {
		TozenProtocolMessage msg = new TozenProtocolMessage(
				TozenMessageType.GAMES);

		int openGamesCount = 0;
		for (Game g : this.games.values())
			if (g.isRunning() == false)
				openGamesCount++;

		msg.games = new TozenProtocolMessage.game[openGamesCount];
		int k = 0;
		for (Game g : this.games.values())
			if (g.isRunning() == false) {
				msg.games[k] = msg.new game();
				msg.games[k].id = g.getId();
				msg.games[k].maxPlayersCount = g.type.maxPlayersCount;
				msg.games[k].name = g.getName();
				msg.games[k].title = g.type.title;
				msg.games[k].playersCount = g.getPlayersCount();
				k++;
			}

		this.boradcastProtocolMessage(msg);
	}

	/**
	 * Creates a new Game
	 * 
	 * @param typeId
	 * @param name
	 * @param admin
	 * @return
	 */
	synchronized public Game createGame(int typeId, String name,
			PlayerThread admin) {
		Game g = GameWrapper.games[typeId].createGame(name, admin, this);
		this.games.put(g.getId(), g);
		this.sendGamesList();
		return this.games.get(g.getId());
	}

	public void sendMessage(int sender, int receiver, String message) {
		if (!this.players.containsKey(receiver))
			throw new NullPointerException();

		this.players.get(receiver).sendMessage(sender, message, false);
	}

	public void sendMessage(int sender, String message) {
		for (PlayerThread p : this.players.values())
			p.sendMessage(sender, message, true);
	}

	synchronized public void removeGame(Game g) {
		this.games.remove(g.getId());
		this.sendPlayersList();
		this.sendGamesList();
	}

	synchronized public void joinGame(PlayerThread player, int gameId)
			throws NullPointerException, NoMorePlayerException {

		if (!(this.games.containsKey(gameId) && this.games.get(gameId)
				.isRunning() == false))
			throw new NullPointerException();

		player.goToGame(this.games.get(gameId));

		this.sendPlayersList();
	}

	public void pingReset(PlayerThread p) {
		pingTimer.put(p, new Date().getTime());
	}

	synchronized public void killPlayer(PlayerThread p) {
		if (p.getPlace() == PlaceEnum.GAME)
			p.goToLobby();

		this.players.remove(p.getPlayerId());
		this.pingTimer.remove(p);
		this.sendPlayersList();
	}
	
	public void kill(){
		final TozenServer parent = this;
		new Thread(new Runnable(){

			@Override
			public void run() {
				for(int i = 5; i > 0; i--){
					System.out.println("Server goes down in " + i + " seconds");
					parent.sendMessage(0, "Server goes down in " + i + " seconds");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				try {
					parent.sendMessage(0, "Server's gone.");
					
					if(parent.socket != null)
						parent.socket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.exit(0);
			}
			
		}).start();
		
	}

}
